/******************************************************************************
 * $Id: tags.c,v 1.222 2004/09/22 01:14:23 jimb Exp $
 * @file
 * @author  Jim Brooks http://www.jimbrooks.org
 * @brief   A set of functions for managing source code tags.
 ******************************************************************************/

#include "common.h"
#include "tags.h"

#include "module.h"
#include "file.h"
#include "widgets.h"
#include "sort.h"
#include "history.h"
#include "statusbar.h"
#include "text.h"
#include "find.h"

#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>

#define READ_STDIN_SIZE  (128 * 1024) /*BUFSIZ*/

/*
 * For a narrower list column (and speed), some ctag types are abbreviated.
 */
static int* validTagTypesLen;		/* table of lengths, built at run-time */
const char* validTagTypes[] =
{
  /*
   * C tag types:
   */
   "func",
   "var",
   "macro",
   "member",
   "enum",
   "typedef",
   "struct",
   "union",
   "proto",
#if 0 
   "define",			/* ctags outputs "macro" instead */
   "externvar",			/* by default, ctags won't produce this */
#endif

  /*
   * C++ tag types:
   */
   "class",
   "namespace",

  /*
   * Java tag types:
   */
   "method",
   "field",
   "interface",
   "package",

  /*
   * Assembly tag types:
   */
   "label",
   "define",
#if 0
   "macro", /* included above */
#endif

  /*
   * Perl tag types:
   */
   "sub",		/* ctags.pl */
   "scalar",	/* ctags.pl */
   "list",		/* ctags.pl */
   "hash",		/* ctags.pl */
   "package",

  /*
   * M4 tag types (ctags.pl):
   */
   "m4_macro",

  /*
   * FORTH tag types (ctags.pl):
   */
   "word",

  /*
   * Is this a non-tag?
   */
   "(NONE)"
};

/*
 * map to ppPixmaps
 */
const int validTagTypePic[]=
{
    FUNCTION_OBJ,//"func",
    VAR_OBJ,//"var",
    MACRO_OBJ,//"macro",
    METHOD_PROTECTED_OBJ,//MEMBERS_OBJ,//"member",
    ENUM_OBJ,//"enum",
    TYPEDEF_OBJ, //"typedef",
    STRUCT_OBJ, //"struct",
    UNION_OBJ, //"union",
    MAKE_TARGET_OBJ,//"proto",
#if 0 
    DEFINE_OBJ,//"define",			/* ctags outputs "macro" instead */
    VAR_OBJ,//"externvar",			/* by default, ctags won't produce this */
#endif

  /*
   * C++ tag types:
   */
   CLASS_OBJ,//"class",
   NAMESPACE_OBJ,//"namespace",

  /*
   * Java tag types:
   */
   METHOD_PROTECTED_OBJ,//"method",
   NAMESPACES_OBJ,//"field",
   MAKE_TARGET_OBJ,//"interface",
   LIB_OBJ,//"package",

  /*
   * Assembly tag types:
   */
   UNKNOWN_OBJ,//"label",
   DEFINE_OBJ,//"define",
#if 0
   "macro", /* included above */
#endif

  /*
   * Perl tag types:
   */
   FUNCTION_OBJ,//"sub",		/* ctags.pl */
   UNKNOWN_OBJ,//"scalar",	/* ctags.pl */
   UNKNOWN_OBJ,//"list",		/* ctags.pl */
   UNKNOWN_OBJ,//"hash",		/* ctags.pl */
   UNKNOWN_OBJ,//"package",

  /*
   * M4 tag types (ctags.pl):
   */
   MACRO_OBJ,//"m4_macro",

  /*
   * FORTH tag types (ctags.pl):
   */
   UNKNOWN_OBJ,//"word",

  /*
   * Is this a non-tag?
   */
   UNKNOWN_OBJ,//"(NONE)"
};

/*****************************************************************************
 * Prepare state for process ctags output.
 *****************************************************************************/
void
PrepareProcessCtags( void )
{
   int			i;

  /*
   * Init tags global vars.
   */
   tagsCnt = 0;
   ctagsLines = 0;
   pHashFuncTagsAll = g_hash_table_new( HashFuncTagsKey, HashFuncTagsCmp );

  /*
   * Build table of lengths of valid tag types (to speed CheckTagType() ).
   */
   validTagTypesLen = g_malloc( ELEMS(validTagTypes) * sizeof(int) );
   for ( i = 0; i < ELEMS(validTagTypes); i++ )
   { validTagTypesLen[i] = strlen( validTagTypes[i] ); }

   return;
}

/*****************************************************************************
 * Process ctags output from a file, resulting in a GList of tag_t structs.
 * @param   pGlistTags (OUT/GLOBAL)
 *          GList of tag_t structs.
 * @param   tagsCnt (OUT/GLOBAL)
 *          Count of links in pGlistTags.
 *****************************************************************************/
void
ProcessCtagsFromFile( char* pFilename )
{
   char*	pCtags;
   guint	ctagsSize;

  /*
   * Were there too many tags last time?
   */
   if ( tooManyTags ) return;

  /*
   * Prepare state.
   */
   PrepareProcessCtags();

  /*
   * Create a list of tags from ctags.
   */
   pCtags = ReadCtagsFromFile( &ctagsSize, /* OUT */
                               pTagsFilename );
   pGlistTags = ProcessCtagsBuf( pCtags, ctagsSize );
   if ( tooManyTags ) WarnTooManyTags();

   return;
}

/******************************************************************************
 * Read ctags -x output from a file.
 * @param   pCtagsSize (out)
 *          Size of ctags output.
 * @param   pFilename
 * @return  Returns pointer to ctags output (or abends).
 ******************************************************************************/
char*
ReadCtagsFromFile( guint* pCtagsSize, /* OUT */
                   char*  pFilename )
{
   gpointer	pBuffer;

   pBuffer =  LoadFile( pFilename,
                        pCtagsSize /* OUT */ );
   if ( pBuffer == NULL ) { Error( "\nCould not open tags file.\n\nTry running 'Hypersrc.pl' instead. \n" ); }

   return pBuffer;
}

/*****************************************************************************
 * Process ctags output from stdin, resulting in a GList of tag_t structs.
 * @param   pGlistTags (OUT/GLOBAL)
 *          GList of tag_t structs.
 * @param   tagsCnt (OUT/GLOBAL)
 *          Count of links in pGlistTags.
 *****************************************************************************/
void
ProcessCtagsFromStdin( void )
{
   char*	pBuf;
   int      bytesRead;
   int      bytesReadMore;
   GList*	pGlistPiece = NULL;

  /*
   * Prepare state.
   */
   PrepareProcessCtags();

  /*
   * Allocate enough memory for a buffer that might be > READ_STDIN_SIZE.
   */
   pBuf = g_malloc( READ_STDIN_SIZE * 4 );

  /*
   * This loop grabs pieces of ctags output, from stdin, and passes each piece
   * to ProcessCtagsBuf().  Each partial GList is appended to what will be
   * the final GList.
   */
   for (;;)
   {
     /*
      * Processing GTK+ events while waiting for input...
      */
      for (;;)
      {
         struct timeval tv = { 0, 1000 }; /* secs, usecs */
         fd_set rdset;

         FD_ZERO( &rdset );
         FD_SET( 0, &rdset );
         if ( select( 0+1, &rdset, NULL, NULL, &tv ) )
            break;
         ProcessPendingEvents();
      }

     /*
      * Read the next bundle of ctags lines from stdin.
      * Break if end of stdin.
      */
      bytesRead = read( STDIN_FILENO, pBuf, READ_STDIN_SIZE );
      if ( bytesRead <= 0 ) break;

     /*
      * Rarely, if only one byte has been read, then read a second byte,
      * to let the following loop assume that at least two bytes were read.
      */
      if ( bytesRead == 1 )
      {
         bytesReadMore = read( STDIN_FILENO, &pBuf[bytesRead], 1 );
         if ( bytesReadMore <= 0 ) goto skip;
         ++bytesRead;
      }

     /*
      * Ctags lines can not be passed truncated, so unless no more input,
      * and if the last char(s) weren't EOL, try to read more chars until
      * an EOL is encountered.
      */
      while (    ! IsEolUnix(  &pBuf[bytesRead-1] )
              && ! IsEolMsdos( &pBuf[bytesRead-2] ) ) /* <-- assumes at least 2 bytes were read */
      {
         bytesReadMore = read( STDIN_FILENO, &pBuf[bytesRead], 1 );
         if ( bytesReadMore <= 0 ) break;
         ++bytesRead;
      }

      skip:

     /*
      * Truncate buffer (though not strictly necessary).
      */
      pBuf[bytesRead] = 0;

     /*
      * Process this piece (which has untruncated lines).
      * If this is not the first piece, then append this GList into the final GList.
      */
      if ( pGlistTags == NULL )
      {
         pGlistTags = ProcessCtagsBuf( pBuf, bytesRead );
         if ( pGlistTags == NULL )
            Error( "Could not process any ctags lines, bailing out!" );
      }
      else
      {
         pGlistPiece = ProcessCtagsBuf( pBuf, bytesRead );
         if ( pGlistPiece == NULL )
            break;
         pGlistTags = g_list_concat( pGlistTags, pGlistPiece ); /* lv */
      }

     /*
      * Stop if there were too many tags.
      */
      if ( tooManyTags )
      {
         WarnTooManyTags();
         break;
      }

     /*
      * Continue loop.
      */
   }

  /*
   * Were any ctag lines processed?
   */
   if ( pGlistTags == NULL )
       Error( "No ctags lines were processed, bailing out!" );

   g_free( pBuf );

   return;
}

/******************************************************************************
 * Process a buffer of lines from ctags.
 * @verbatim
 * - Counts each ctags line (++ctagsLine)
 * - Creates a glist of tag_t structs.
 * - Creates a hash table containing info about each module.
 *   This hash table is the first level of the func graph.
 * - Stops if tag count exceeds a limit.
 * @endverbatim
 * @param   pCtags
 *          Pointer to raw ctags output.
 * @param   ctagsSize
 *          Size of ctags output.
 * @param   pHashModules (GLOBAL)
 * @param   tooManyTags (OUT/GLOBAL)
 *          Will be TRUE if this function stopped
 *          when there were too many tags.
 * @param   ctagsLine (OUT/GLOBAL)
 *          Incremented.
 * @return  Returns a GList.
 ******************************************************************************/
GList*
ProcessCtagsBuf( char* pCtags,
                 guint ctagsSize )
{
   const char*	pLine		= pCtags;
   GList*		pGlist		= NULL;
   tag_t*		pTag		= NULL;		/* threading precludes making these static! */
   int			error		= 0;

  /*
   * Ensure the tags glist pointer is NULL.
   * (The GLIB list functions will create a list if the passed list pointer is NULL.)
   */
   pGlist = NULL;

  /*
   * Decompose each ctags line.
   * Add each decomposed part to a tag_t struct.
   */
   for (;;)
   {
     /*
      * Count this ctags line, and stop if there were too many tags.
      */
      ++ctagsLines;
      if ( ctagsLines > maxTags )
      {
         tooManyTags = TRUE;
         break;
      }

     /*
      * Is this ctags line empty? (very unexpected)
      */
      if ( IsLineEmpty( pLine ) )
         break;

     /*
      * Add this ctags line to a glist.
      * Later, the glist will be used to create a list widget.
      */
      error = ProcessCtagsLine( pLine, &pGlist, &pTag );
      if ( error != FUNC_OK )
      {
        /*
         * Too many modules?
         */
         if ( error == FUNC_FAIL_TOO_MANY_MODULES )
         {
            static int warned = 0;
            if ( warned++ == 0 )
            {
               SetStatusbarInitialMsg( _("There were too many modules.  Try -max-modules.") );
               Warning( "There were too many modules (maximum is %d).  Try -max-modules.", maxModules );
            }
            break;
         }

        /*
         * Oops, unable to process this ctags line.
         * This is considered a recoverable error (albeit unexpected)
         * only if previous ctags lines have been processed successfully,
         * which is indicated if the list does contains some elements.
         */
         if ( pGlist == NULL )
            Error( "Could not process any ctags lines, bailing out!" );

         SetStatusbarInitialMsg( _("Could not process a ctags line, proceeding anyway.") );
      }

     /*
      * Move to next line in ctags.
      */
      pLine = NextLine( pLine, MAX_CTAGS_LINE_LEN );

     /*
      * Break if no more lines in ctags.
      */
      if (    pLine == NULL
           || (pLine - pCtags) >= ctagsSize ) break;
   }

#if 1
   DebugMsg( "tag lines processed = %d", ctagsLines );
#endif

   return pGlist;
}

/******************************************************************************
 * @verbatim
 * Steps that are done for every line from ctags:
 * - Derives from a ctags line a tag_t struct which is appended to a list (ppGlist).
 * - When the next module is encountered (the module field in a ctags line alters),
 *   the module is hashed (pHashModules).
 * - Tags of functions/macros are hashed to speed graphing functions (module_t.pHashFuncTags).
 * This is an example of a line from "ctags -x":
 *  tag:    type:       line:  module:     (ignored:)
 * "main    function    14     dummy.c     int main( int argc, char* argv[] )"
 * @endverbatim
 *
 * @param   pLine
 *          Pointer to start of a line in ctags.
 * @param   ppGList (IN/OUT)
 *          Pointer to a GList pointer that is a list of tag_t structs.
 * @param   ppTag (OUT)
 *          Pointer to the resulting tag_t struct created from ctags line.
 * @param   pHashModules (GLOBAL/OUT)
 * @return  FUNC_OK or FUNC_FAIL
 ******************************************************************************/
FUNC_X
ProcessCtagsLine( const char* pLine,
                  GList**     ppGlist, /*IN/OUT*/
                  tag_t**     ppTag )  /*OUT*/
{
   tag_t*			pTag				= NULL;
   const char*		pWord				= pLine;
   int				sizeWord;
   int				lineFieldRetries	= 0;
/*---these static vars preclude threading---------------------------------------*/
   static module_t*	pModuleParsing		= NULL;		/* the module being parsed */
   static GList*	pGlistLastTag		= NULL;		/* for fast_g_list_append() */
/*---these static vars preclude threading---------------------------------------*/
#ifdef HYPERSRC_THREADS
#error "Remove static vars out of ProcessCtagsLine() which preclude threading."
#error "Threading can be reinstated by moving the static vars into ProcessCtagsBuf()."
#endif

  /*
   * Allocate a tag_t struct (to be added to the list after it is successfully filled).
   */
   *ppTag = pTag = AllocTagStruct();

  /*
   * Process the tag name field.
   */
   sizeWord = ParseWord( pWord, &pWord, MAX_CTAGS_FIELD_LEN );
   if ( sizeWord > 0 )
   {
      pTag->pName = CreateString( sizeWord );
      CopyChars( pTag->pName, pWord, sizeWord );
   }
   else
      return FUNC_FAIL;
   pWord = PastWord( pWord, sizeWord );

  /*
   * Process the type field.
   */
   sizeWord = ParseWord( pWord, &pWord, MAX_CTAGS_FIELD_LEN );
   if ( sizeWord > 0 )
   {
     /*
      * Try to recognize the tag type.
      */
      pTag->pType = (char*) CheckTagType( pWord );		/* returns ptr to static tag type */
      if ( pTag->pType == NULL )
      {
        /*
         * Tag was not immediately recognized.
         * Is this the case in which Exuberant ctags outputs a multi-word
         * tag name for a C++ operator?  If so, the ctags line will have the format,
         * "operator X function nnn", in which "X" can be one or more words.
         */
         if ( FindSubstr( pTag->pName, "operator" ) )
         {
           /*
            * Reassign the tag name with "operator X"
            * (ie the string that precedes the "function" field.)
            */
            DeleteString( pTag->pName );
            pWord = FindSubstr( pLine, "function" );		/* pWord -> "function" */
#ifdef HYPERSRC_PEDANTIC
            if ( pWord == NULL  ||  pWord > NextLine( pLine, MAX_CTAGS_LINE_LEN ) ) goto unrecognized_tag;
#else
            if ( pWord == NULL ) goto unrecognized_tag;
#endif
            pTag->pName = CreateString( pWord - pLine );
            CopyChars( pTag->pName, pLine, pWord - pLine );

           /*
            * For this C++ operator, assign the tag type field as "function".
            */
            pTag->pType = "func";

           /*
            * Prepare to advance pWord past the "function" field.
            */
            sizeWord = sizeof("function")-1;

           /*
            * Successfully parsed "operator X".
            */
         }
         else
         {
           /*
            * Tag is not "operator X" and is not recognized.
            * WARNING: Ignoring this error could result in a failure later,
            *          but acceptAllTags is necessary to browse non-C src.
            */
            if ( !acceptAllTags )
               goto unrecognized_tag;

           /*
            * Fill tag_t.pType with unrecognized tag type.
            */
            pTag->pType = CreateString( sizeWord );
            CopyChars( pTag->pType, pWord, sizeWord );
         }
      }
   }
   else
      return FUNC_FAIL;
   pWord = PastWord( pWord, sizeWord );

  /*
   * Process the line number field.
   */
retry_line_field:
   sizeWord = ParseWord( pWord, &pWord, MAX_CTAGS_FIELD_LEN );
   if ( sizeWord > 0 )
   {
     /*
      * Try to convert this field into an integer.
      */
      pTag->lineInt = atoi( pWord );

     /*
      * The result should be > 0.
      */
      if ( pTag->lineInt == 0 )
      {
        /*
         * A new multiword tag type might have been encountered.
         * Retry the next few words.  Hopefully, one might be the line # field.
         * (By neglect, only the first word of such a tag type will be shown in list.)
         */
         if ( lineFieldRetries++ < 3 )
         {
            pWord = PastWord( pWord, sizeWord );
            goto retry_line_field;
         }
         return FUNC_FAIL;
      }
   }
   else
      return FUNC_FAIL;
   pWord = PastWord( pWord, sizeWord );

  /*
   * Process the module (filename) field.
   * TODO: Won't correctly parse filenames with spaces.
   */
   sizeWord = ParseWord( pWord, &pWord, MAX_CTAGS_FIELD_LEN );
   if ( sizeWord > 0 )
   {
     /*
      * Has ctags began parsing another module?
      */
      if ( !pModuleParsing  ||  MEMNE( pModuleParsing->pModuleName, pWord, sizeWord ) )
      {
        /*
         * ctags began parsing another module.
         * TODO Does this look like a module?
         */
         pTag->pModuleName = CreateString( sizeWord );		/* allocate string for module name */
         CopyChars( pTag->pModuleName, pWord, sizeWord );	/* InsertModuleIntoHash() requires permanently allocated string */

         pModuleParsing = HashDebutModule( pTag->pModuleName );
         if ( !pModuleParsing )
            return FUNC_FAIL_TOO_MANY_MODULES;

#if !defined(HYPERSRC_SPEED)  &&  !defined(HYPERSRC_PROFILE)
         StartupStatusMsg( _("Processing tags: "), pTag->pModuleName, " " );
#endif
      }
      else
      {
        /*
         * ctags is still parsing the same module.
         */
         pTag->pModuleName = pModuleParsing->pModuleName;	/* reuse allocated string */
      }
   }
   else
      return FUNC_FAIL;
   pWord = PastWord( pWord, sizeWord );

  /*
   * Append completed tag_t to tag list.
   */
   *ppGlist = fast_g_list_append( *ppGlist,
                                  (gpointer) pTag,
                                  &pGlistLastTag /*IN/OUT*/ );

  /*
   * Avoid duplicating strings in multiple tags.
   */
#ifndef HYPERSRC_SPEED
   if ( conserveMemory ) MinimizeTagStruct( pTag, *ppGlist );
#endif

  /*
   * Tags of functions (and #define macros and C++/Java methods)
   * are hashed in order to speed graphing functions.
   */

   if ( IsFuncLikeTag( pTag ) )
   {
     /*
      * If -class-qual was passed, ctags will make an extra fully-qualified tag
      * of a C++ class func in addition to a normal tag, which will confuse
      * the functree code that matches a line with a func def.
      * Therefore, don't insert fully-qualified tags (class::func).
      */
      if (    !classQual 
           || index( pTag->pName, ':' ) == NULL )
      {
        /*
         * Insert the tag into two different hash tables:
         * one is per-module, the other is pan-module.
         */
         g_hash_table_insert( pModuleParsing->pHashFuncTags,
                              pTag->pName, /* key */
                              pTag );      /* value */
         g_hash_table_insert( pHashFuncTagsAll,
                              pTag->pName, /* key */
                              pTag );      /* value */
      }
   }

  /*
   * Count this tag (so that the slow g_list_length() can be avoided).
   */
   ++tagsCnt;

   return FUNC_OK;

unrecognized_tag:
   if ( ExtractLine( pLine, MAX_CTAGS_LINE_LEN ) )
   { Error( "Couldn't recognize tag.\nTry passing -accept-all-tags (as a last resort).\nctags line:\n%s\n", ExtractLine( pLine, MAX_CTAGS_LINE_LEN ) ); }
   else
   { Error( "Couldn't recognize tag.\nTry passing -accept-all-tags (as a last resort)." ); }
   return FUNC_FAIL; /* shutup compiler */
}

/*****************************************************************************
 * Minimize the amount of memory needed for a tag struct -- at great expense of speed.
 *****************************************************************************/
void
MinimizeTagStruct( tag_t* pTag,
                   GList* pGlist )
{
   GList*		pGlistTmp;

  /*
   * (module name strings are no longer duplicated)
   */

  /*
   * Would this duplicate a tag name string already in a prior tag struct?
   */
   for ( pGlistTmp = pGlist;
         (void*)pGlistTmp->data != (void*)pTag;		/* don't compare the tag against itself! */
         pGlistTmp = pGlistTmp->next )
   {
     /*
      * Found duplicate tag name string?
      */
      if ( CompareStrings( pTag->pName, ((tag_t*)(pGlistTmp->data))->pName ) == 0 )
      {
         DeleteString( pTag->pName );
         pTag->pName = ((tag_t*)pGlistTmp->data)->pName;
         break;
      }
   }

   return;
}

/******************************************************************************
 * Allocate a tag_t struct.
 ******************************************************************************/
tag_t*
AllocTagStruct( void )
{
  /*
   * The tag_t struct is initially all clear.
   */
   return g_malloc0( sizeof(tag_t) );
}

/******************************************************************************
 * Free a tag_t struct.
 ******************************************************************************/
void
FreeTagStruct( tag_t* pTag )
{
  /*
   * TODO
   */
}

/******************************************************************************
 * Assign the members of tag_t struct.
 * Memory for each member will allocated here.
 ******************************************************************************/
void
FillTagStruct( tag_t*  pTag,
               gchar*  pName,
               gint    lineNum,
               gchar*  pType,
               gchar*  pModuleName )
{
   /* tag_t.pName */
   pTag->pName = CreateString( StrLen(pName) );
   strcpy( pTag->pName, pName );

   /* tag_t.pType */
   pTag->pType = CreateString( StrLen(pType) );
   strcpy( pTag->pType, pType );

   /* tag_t.lineInt */
   pTag->lineInt = lineNum;

   /* tag_t.pModuleName */
   pTag->pModuleName = CreateString( StrLen(pModuleName) );
   strcpy( pTag->pModuleName, pModuleName );

   return;
}

/*****************************************************************************
 * @return True if tag is fabricated.
 *****************************************************************************/
gboolean
IsTagFabricated( tag_t* pTag )
{
   if ( pTag  &&  STREQ( pTag->pModuleName, "N/A" ) )
      return TRUE;
   return FALSE;
}

/*****************************************************************************
 * Fabricate a tag of a function that is called but its definition wasn't tagged.
 * In order to constrain the amount of tags appearing in the clist widget,
 * fabricated tags are not linked with other tags.
 *****************************************************************************/
tag_t*
FabricateFuncCallTag( char* pFuncCallId,
                      int   lenFuncCallId )
{
   tag_t*	pTagNew;
   tag_t*	pTagHashed;

#ifdef HYPERSRC_DEBUG
g_return_val_if_fail( pHashTagsFabricated, NULL );
#endif

  /*
   * Lots of memory can be conserved by avoiding refabricating same-named tags
   * (most of which are library calls).
   */
   if ( conserveMemory )
   {
     /*
      * Was a same-name fabricated tag hashed?
      */
      pTagHashed = (tag_t*) g_hash_table_lookup( pHashTagsFabricated,
                                                 pFuncCallId );
      if ( pTagHashed )
         return pTagHashed;

     /*
      * Not found, fall thru to fabricate...
      */
   }

  /*
   * Fabricate a new tag.
   */
   pTagNew = AllocTagStruct();

   pTagNew->pName       = FastCloneString( pFuncCallId, lenFuncCallId );
   pTagNew->pType       = "call";
   pTagNew->lineInt     =  999;		/* bogus */
   pTagNew->pModuleName = "N/A";	/* bogus */

  /*
   * If conserving memory, link the fabricated tag for possible reuse.
   */
   if ( conserveMemory )
      g_hash_table_insert( pHashTagsFabricated,
                           pTagNew->pName,	/* key (not pFuncCallId which is temp!!) */
                           pTagNew );		/* value */

   return pTagNew;
}

/******************************************************************************
 * Ensure that a tag type (extracted from ctags) is recognizable.
 * @param   pTagType
 *          Pointer to tag type from ctags (doesn't need to be null-terminated)
 * @return  If the tag type is valid, returns a pointer to a matching string
 *          IN STATIC MEMORY (to conserve memory by not allocating a new string).
 ******************************************************************************/
const char*
CheckTagType( const char* pTagType )
{
   int i;

#if 0
  /*
   * ## ProcessCtagsLine() depends on CheckTagType()
   * ## returning failure when ctags outputs "operator X".
   * ## Otherwise, ProcessCtagsLine() will malfunction.
   */
   if ( acceptAllTags ) return FUNC_OK;
#endif

   for ( i = 0; i < ELEMS(validTagTypes); i++ )
   {
      if ( memcmp( pTagType, validTagTypes[i], validTagTypesLen[i] ) == 0 )
         return validTagTypes[i];
   }

  /*
   * This tag type was not recognized.
   */
   return NULL;
}

/*****************************************************************************
 * @return True if a tag is of a func/macro/method.
 *****************************************************************************/
gboolean
IsFuncLikeTag( tag_t* pTag )
{
   if (    pTag->pType[0] == 'f'
        || pTag->pType[0] == 'm' )
      return    MEMEQ(pTag->pType, "func", 4)   /* function */
             || MEMEQ(pTag->pType, "macr", 4)   /* macro */
             || MEMEQ(pTag->pType, "meth", 4);  /* method */
   else
      return FALSE;

#if 0
/* --- THIS ISN'T PORTABLE: THE POINTER COULD BE MISALIGNED */
/* --- WHICH WILL FAULT ON SOME ARCHITECTURES.  LEFT AS FYI. */
   guint32	type4;
   type4 = *(guint32*)pTag->pType;
   return    type4 == ('f'|'u'<<8|'n'<<16|'c'<<24)
          || type4 == ('m'|'a'<<8|'c'<<16|'r'<<24)
          || type4 == ('m'|'e'<<8|'t'<<16|'h'<<24);
#endif
}

/*****************************************************************************
 * @return True if a tag is of macro.
 *****************************************************************************/
gboolean
IsMacroTag( tag_t* pTag )
{
   return pTag->pType[0] == 'm' && MEMEQ(pTag->pType, "macr", 4);  /* macro */
}

/*****************************************************************************
 * Find a tag.
 * Directly searches thru a glist.
 * @param   pTagName
 * @param   pTagType
 *          Pass NULL to match any.
 *****************************************************************************/
tag_t*
FindTag( char* pTagName,
         char* pTagType )
{
   GList*	pGlist;

   for( pGlist = pGlistTags;
        pGlist;
        pGlist = pGlist->next )
   {
#ifdef HYPERSRC_PEDANTIC
g_assert( pGlist->data   &&  ((tag_t*)pGlist->data)->pName  &&  ((tag_t*)pGlist->data)->pType );
#endif

     /*
      * Compare tag name.
      */
      if ( STREQ( pTagName, ((tag_t*)pGlist->data)->pName ) )
      {
        /*
         * Tag name matches.  Compare tag type (if specified).
         */
         if ( pTagType )
            if ( STRNE( pTagType, ((tag_t*)pGlist->data)->pType ) )
               return NULL;

        /*
         * Return matching tag.
         */
         return (tag_t*) pGlist->data;
      }
   }

   return NULL;
}

/*****************************************************************************
 * Find the tag of a function/macro/method in one module.
 * @param   pName
 *          Name of function.
 * @param   pModule (module_t)
 *          Which module which is supposed to contain the tagged function.
 *****************************************************************************/
tag_t*
FindFuncTag( char*     pName,
             module_t* pModule )
{
   return (tag_t*) g_hash_table_lookup( pModule->pHashFuncTags, pName );
}

/*****************************************************************************
 * Find the first matching function tag in ANY OTHER module besides the current one.
 * This would be called if FindFuncTag() fails.
 * @param   pName
 *          Name of function.
 * @param   pLinkCurrentModule
 *          Pointer to a link in pGlistModuleNames that identifies the current module
 *          which the function is known to not reside in.
 *****************************************************************************/
tag_t*
FindFuncTagAnyOtherModule( char*  pName,
                           GList* pLinkModuleCurrent )
{
#if 1
   return (tag_t*) g_hash_table_lookup( pHashFuncTagsAll, pName );
#else
   /* --- The following implementation is much slower, but it can find   --- */
   /* --- closer matches in the event multiple tags share the same name. --- */

   GList*		pPrev;
   GList*		pNext;
   tag_t*		pTag;
   module_t*	pModule;

  /*
   * In the list of module names, search outwards in both directions from
   * the current module.  The rationale is that the sought function tag
   * is defined in a nearby module (the list of modules name is sorted
   * by pathname by way of alphabetical).
   */
   pPrev = pLinkModuleCurrent->prev;
   pNext = pLinkModuleCurrent->next;

   if ( pPrev == NULL ) goto backward_done;
   if ( pNext == NULL ) goto forward_done;

   for (;;)
   {
     /*
      * Search backward.
      */
      pModule = g_hash_table_lookup( pHashModules, pPrev->data );
      if ( (pTag = FindFuncTag( pName, pModule )) )
         return pTag;
      pPrev = pPrev->prev;
      if ( !pPrev )
         goto backward_done;

     /*
      * Search forward.
      */
      pModule = g_hash_table_lookup( pHashModules, pNext->data );
      if ( (pTag = FindFuncTag( pName, pModule )) )
         return pTag;
      pNext = pNext->next;
      if ( !pNext )
         goto forward_done;
   }
   return NULL;

backward_done:
   for ( ; pNext; )
   {
     /*
      * Search forward.
      */
      pModule = g_hash_table_lookup( pHashModules, pNext->data );
      if ( (pTag = FindFuncTag( pName, pModule )) )
         return pTag;
      pNext = pNext->next;
   }
   return NULL;

forward_done:
   for ( ; pPrev; )
   {
     /*
      * Search backward.
      */
      pModule = g_hash_table_lookup( pHashModules, pPrev->data );
      if ( (pTag = FindFuncTag( pName, pModule )) )
         return pTag;
      pPrev = pPrev->prev;
   }
   return NULL;
#endif
}

/******************************************************************************
 * The "hash function" for the hash table of function/macro tags.
 ******************************************************************************/
guint
HashFuncTagsKey( gconstpointer pTagName )
{
   return ElfHash( pTagName, 0 );
}

/******************************************************************************
 * The key comparison function for the hash table of function/macro tags.
 ******************************************************************************/
gint
HashFuncTagsCmp( gconstpointer pTagName1,
                 gconstpointer pTagName2 )
{
   return STREQ( pTagName1, pTagName2 );
}

/*****************************************************************************
 * Goto the tag which is under the cursor (of the text widget).
 * Invoked when the toolbar button "Goto Tag" is selected.
 *****************************************************************************/
void
GotoTagUnderCursor( void )
{
   void GotoTagUnderCursor___( void );

  /*
   * For stability, serialize this function.
   */
   if ( busy_GotoTagUnderCursor ) { DebugMsg( "GotoTagUnderCursor() is busy." ); return; }
   busy_GotoTagUnderCursor = TRUE;
   GotoTagUnderCursor___();
   busy_GotoTagUnderCursor = FALSE;
}

/* 改变工作方式：在哪个Text中的光标，就在本窗口中跳转*/
void
GotoTagUnderCursor___( void )
{
   GtkWidget*	pWidgetTextActivePrev   = pWidgetTextActive;
   GString*		pGstr					= NULL;
   gchar*		pText					= NULL;
   guint		textLen;
   guint		cursorIdx;
   gchar		tagName[MAX_TAG_NAME_LEN];
   int			row;

  /*
   * Ensure the text widget is created and isn't empty.
   */
   if ( IsTextWidgetEmpty() )
   {
      PrintStatusbar( _("Text widget is empty") );
      return;
   }

  /*
   * Obtain contents of text widget.
   */
   pText = ActiveTextWidgetContents( &textLen );
   if ( pText == NULL  ||  textLen > MAX_TEXT_BUFFER_LEN ) goto error;

  /*
   * Get index of cursor.
   */
   cursorIdx = OffsetAtCursor();//TextCursorIdx();
   if ( cursorIdx < 0  ||  cursorIdx > textLen ) goto error;

  /*
   * Scoop word at cursor.
   * Return if cursor is not over a word.
   */
   pGstr = ScoopWordAtCursor( MAX_TAG_NAME_LEN,
                              pText,
                              textLen,
                              cursorIdx );
   if ( pGstr == NULL )
   {
      PrintStatusbar( _("Please move the cursor over a word that might be a tag.") );
      goto quit;
   }

   /*-----*/

   gtk_clist_freeze( GTK_CLIST( pWidgetClistTags ) );
   gtk_clist_freeze( GTK_CLIST( pWidgetClistModules ) );

  /*
   * Record the current cursor position into the tags history.
   * This tag is needed so that the user can return to previous cursor positions
   * (via Prev Tag button).
   */
   RecordCursorInTagHistory(_( "(recorded position)" ));

  /*
   * Should the tagged line be shown in the main or aux text widget?
   */
//   SwitchActiveTextWidget( SWITCH_PER_AUX_TEXT_KEY );
     SwitchActiveTextWidget( DONT_SWITCH_UNLESS_AUX_TEXT_KEY );

  /*
   * Select the row in the clist which contains the tag.
   * For SearchClistWidget(), create a string to hold the tag name.
   */
   if ( tagsCnt > TAGS_VERY_MANY ) PrintStatusbar(_( "Going to tag..." ));

   CopyChars( tagName, pGstr->str, pGstr->len );
   row = \
   FindTagCheckMultipleMatches( tagName,
                                SEEKING_ANY_TAG,
                                NULL,
                                widgetTagsClistSelectedRow,
                                TRUE,	/* be exact */
                                TRUE,   /* (case N/A) */
                                autoSelectNotebookPage,
                                _("Multiple tags share the same name (press F5 for next one)." ));

   if (    (tagsCnt > TAGS_VERY_MANY)
        && GetStatusbarMsg()
        && CompareStrings( GetStatusbarMsg(), _("Going to tag...") ) == 0 )
      MostlyClearStatusbar();

   gtk_clist_thaw( GTK_CLIST( pWidgetClistTags ) );
   gtk_clist_thaw( GTK_CLIST( pWidgetClistModules ) );

  /*
   * Restore the active text widget.
   */
   pWidgetTextActive = pWidgetTextActivePrev;
   SetActiveTextWidget( pWidgetTextActive );

  /*
   * If the search failed then remove the tag of the current position
   * because since no hyperlink was done, the current position won't change.
   */
   if ( row == ROW_NONE)
   {
      UndoRecordCursorInTagHistory();
      PrintStatusbarThirds( "'", tagName, _("' is not tagged" ));
   }

  /*
   * Free mem, return.
   */
quit:
   if ( pGstr ) g_string_free( pGstr, TRUE );
   return;

  /*
   * Error occurred, but try to continue.
   */
error:
   WarningMalfunction();
   return;
}


/*****************************************************************************
 * Goto the tag which is under the cursor (of the text widget).
 * Invoked when the toolbar button "Goto Tag" is selected.
 * 用于显示 TextMain 中光标处的 Tag 到 TextAux 中。
 *****************************************************************************/
void
GotoTagUnderCursor2( void )
{
   void GotoTagUnderCursor___2( void );

  /*
   * For stability, serialize this function.
   */
   if ( busy_GotoTagUnderCursor ) { DebugMsg( "GotoTagUnderCursor() is busy." ); return; }
   busy_GotoTagUnderCursor = TRUE;
   GotoTagUnderCursor___2();
   busy_GotoTagUnderCursor = FALSE;
}

/* 改变工作方式：在哪个Text中的光标，就在本窗口中跳转*/
void
GotoTagUnderCursor___2( void )
{
   GtkWidget*	pWidgetTextActivePrev   = pWidgetTextActive;
   GString*		pGstr					= NULL;
   gchar*		pText					= NULL;
   guint		textLen;
   guint		cursorIdx;
   gchar		tagName[MAX_TAG_NAME_LEN];
   int			row;

  /*
   * Ensure the text widget is created and isn't empty.
   */
   if ( IsTextWidgetEmpty() )
   {
      PrintStatusbar( "Text widget is empty" );
      return;
   }

  /*
   * Obtain contents of text widget.
   */
   pText = ActiveTextWidgetContents( &textLen );
   if ( pText == NULL  ||  textLen > MAX_TEXT_BUFFER_LEN ) goto error;

  /*
   * Get index of cursor.
   */
   cursorIdx = OffsetAtCursor();//TextCursorIdx();
   if ( cursorIdx < 0  ||  cursorIdx > textLen ) goto error;

  /*
   * Scoop word at cursor.
   * Return if cursor is not over a word.
   */
   pGstr = ScoopWordAtCursor( MAX_TAG_NAME_LEN,
                              pText,
                              textLen,
                              cursorIdx );
   if ( pGstr == NULL )
   {
      PrintStatusbar( _("Please move the cursor over a word that might be a tag.") );
      goto quit;
   }

   /*-----*/

   gtk_clist_freeze( GTK_CLIST( pWidgetClistTags ) );
   gtk_clist_freeze( GTK_CLIST( pWidgetClistModules ) );

  /*
   * Record the current cursor position into the tags history.
   * This tag is needed so that the user can return to previous cursor positions
   * (via Prev Tag button).
   */
   RecordCursorInTagHistory(_( "(recorded position)" ));

  /*
   * Should the tagged line be shown in the main or aux text widget?
   */
//   SwitchActiveTextWidget( SWITCH_PER_AUX_TEXT_KEY );
//     SwitchActiveTextWidget( DONT_SWITCH_UNLESS_AUX_TEXT_KEY );
    SetActiveTextWidget( pWidgetTextAux);
    
  /*
   * Select the row in the clist which contains the tag.
   * For SearchClistWidget(), create a string to hold the tag name.
   */
   if ( tagsCnt > TAGS_VERY_MANY ) PrintStatusbar(_( "Going to tag..." ));

   CopyChars( tagName, pGstr->str, pGstr->len );
   row = \
   FindTagCheckMultipleMatches( tagName,
                                SEEKING_ANY_TAG,
                                NULL,
                                widgetTagsClistSelectedRow,
                                TRUE,	/* be exact */
                                TRUE,   /* (case N/A) */
                                autoSelectNotebookPage,
                                _("Multiple tags share the same name (press F5 for next one)." ));

   if (    (tagsCnt > TAGS_VERY_MANY)
        && GetStatusbarMsg()
        && CompareStrings( GetStatusbarMsg(), _("Going to tag...") ) == 0 )
      MostlyClearStatusbar();

   gtk_clist_thaw( GTK_CLIST( pWidgetClistTags ) );
   gtk_clist_thaw( GTK_CLIST( pWidgetClistModules ) );

  /*
   * Restore the active text widget.
   */
   pWidgetTextActive = pWidgetTextActivePrev;
   SetActiveTextWidget( pWidgetTextActive );

  /*
   * If the search failed then remove the tag of the current position
   * because since no hyperlink was done, the current position won't change.
   */
   if ( row == ROW_NONE)
   {
      UndoRecordCursorInTagHistory();
      PrintStatusbarThirds( "'", tagName, _("' is not tagged" ));
   }

  /*
   * Free mem, return.
   */
quit:
   if ( pGstr ) g_string_free( pGstr, TRUE );
   return;

  /*
   * Error occurred, but try to continue.
   */
error:
   WarningMalfunction();
   return;
}



/******************************************************************************
 * Resort the tag glist according to one of: tag name, tag type, or module.
 * The resort is accomplished by creating a new glist in order to use
 * g_list_insert_sort() to do the sorting work.
 * @param   sort
 *          One of SORT_*.
 * @param   pGlistTags (IN/OUT/GLOBAL)
 *          The current tags glist will be freed and this will be repointed
 *          to the new sorted glist.
 * @param   sort1 (OUT/GLOBAL)
 * @param   sort2 (OUT/GLOBAL)
 * @param   sort3 (OUT/GLOBAL)
 ******************************************************************************/
void
ResortTagsGlist( int sort )
{
  /*
   * Assign the sort vars appropriately.
   */
   switch ( sort )
   {
      case SORT_TAG:
         sort1    = SORT_TAG;
         sort2    = SORT_TYPE;
         sort3    = SORT_MODULE;
         break;

      case SORT_TYPE:
         sort1    = SORT_TYPE;
         sort2    = SORT_TAG;
         sort3    = SORT_MODULE;
         break;

      case SORT_MODULE:
         sort1    = SORT_MODULE;
         sort2    = SORT_TYPE;
         sort3    = SORT_TAG;
         break;

      default:
         g_assert(0);
   }

  /*
   * Prepare for displaying the completion percentage of sorted tags
   * in the statusbar.
   */
   tagsSorted			  = 0;
   tagsSortedPercentShown = 0;

  /*
   * Sort the tags.
   */
   pGlistTags = InsertionSortGlist( pGlistTags );

   MostlyClearStatusbar();

   return;
}

/******************************************************************************
 * Compare two tag_t structs, using up to 3 levels of comparisons.
 * @param   sort1 (GLOBAL)
 * @param   sort2 (GLOBAL)
 * @param   sort3 (GLOBAL)
 ******************************************************************************/
gint
ResortTagsGlistCompare( gconstpointer pTag1,
                        gconstpointer pTag2 )
{
   gint		rv;

  /*
   * Try the first-level sort.  Return if the tag fields are inequal.
   */
   rv = CompareTags( (const tag_t*)pTag1,
                     (const tag_t*)pTag2,
                     sort1 );
   if ( rv ) return rv;

  /*
   * Try the second-level sort.  Return if the tag fields are inequal.
   */
   rv = CompareTags( (const tag_t*)pTag1,
                     (const tag_t*)pTag2,
                     sort2 );
   if ( rv ) return rv;

  /*
   * Last, try the third-level sort.
   */
   rv = CompareTags( (const tag_t*)pTag1,
                     (const tag_t*)pTag2,
                     sort3 );
   return rv;
}

/******************************************************************************
 * Compare the members of two tag_t structs.
 * @param   pTag1
 * @param   pTag2
 * @param   member
 *          Which member of the tag_t struct, use one of SORT_*.
 * @return  0 if equal
 ******************************************************************************/
inline gint
CompareTags( const tag_t* pTag1,
             const tag_t* pTag2,
             int          member )
{
   switch ( member )
   {
      case SORT_TAG:
         return CompareStrings( pTag1->pName,
                                pTag2->pName );

      case SORT_TYPE:
         return CompareStrings( pTag1->pType,
                                pTag2->pType );

      case SORT_MODULE:
         return CompareStrings( pTag1->pModuleName,
                                pTag2->pModuleName );

      default:
         g_assert(0);
   }

   return 0; /* shutup compiler */
}

/*****************************************************************************
 * Warn (only once) that there were too many tags to process.
 *****************************************************************************/
void
WarnTooManyTags( void )
{
   static gboolean warned = FALSE;
   static char str[] = "There were too many tags (pass -max-tags to increase limit).";

   if ( ! warned )
   {
      warned = TRUE;
      SetStatusbarInitialMsg( str );
      Warning( str );
   }
   return;
}

/* =========================================================================*/

#include "xpm_data.h"

static pair_t ppPixmaps[ N_XPM_TYPE ];

static char** xpm_data[]=
{
 typedef_obj_xpm,
 types_obj_xpm,
 union_obj_xpm,
 var_declare_obj_xpm,
 var_obj_xpm,
 unknown_obj_xpm,
 enum_obj_xpm,
 class_obj_xpm,
 function_obj_xpm,
 macro_obj_xpm,
 struct_obj_xpm,
 define_obj_xpm,
 include_obj_xpm,
 bin_file_obj_xpm,
 c_file_obj_xpm,
 h_file_obj_xpm,
 folder_obj_xpm,
 file_obj_xpm,
 
 namespace_obj_xpm,
 namespaces_obj_xpm,
 method_public_obj_xpm,
 method_protected_obj_xpm,
 method_private_obj_xpm,
 members_obj_xpm,
 
 make_target_obj_xpm,
 lib_obj_xpm,
};

void
InitPixmaps()
{
    GdkPixmap *pPixmap;
    GdkBitmap *pMask;
    
    int i = 0;
    g_assert( N_XPM_TYPE == ELEMS( xpm_data ) );
    
    for ( i = 0; i < N_XPM_TYPE; ++i )
    {

        pPixmap = gdk_pixmap_create_from_xpm_d(GTK_WIDGET(pWidgetMain)->window,
                                               //GTK_CLIST(list)->clist_window,
                                               &pMask,
                                               NULL,    //&(GTK_WIDGET)data->style->white,
                                               xpm_data[ i ] );
        if( NULL == pPixmap )
            DebugMsg("%s: Create pixmap err.", __func__);

        ppPixmaps[ i ].data1 = pPixmap;
        ppPixmaps[ i ].data2 = pMask;
    }
}


/******************************************************************************
 * which type?
 * func/macro/class/struct/enum/typedef/method ...
 ******************************************************************************/
pair_t* 
GetPixmapByType( gchar * pType )
{
    if ( NULL == pType )
        return &ppPixmaps[ UNKNOWN_OBJ ];
    
    int i = 0;
    for ( i=0; i < ELEMS( validTagTypes ); ++i )
    {
        if ( memcmp( pType, validTagTypes[i], validTagTypesLen[i] ) == 0 )
            return &ppPixmaps[ validTagTypePic[i] ];
    }
    
    return &ppPixmaps[ UNKNOWN_OBJ ];
}

/******************************************************************************
 * filename like *.c/ *.h/ *.cpp/ *.java/ *.pl/ *.txt/Makefile ...
 * 优先使用扩展名进行判断，还有一些众所周知文件名，否则使用 gnome_vfs 来判断
 ******************************************************************************/
pair_t*
GetPixmapByFileName( gchar * pType )
{
    if ( NULL == pType )
        return &ppPixmaps[ UNKNOWN_OBJ ];

    int srcKind = WhichKindOfSrcFile( pType );
    
    switch( srcKind )
    {
        case SRC_FILE_C:
            return &ppPixmaps[ C_FILE_OBJ ];
            
        case SRC_FILE_CPLUSPLUS:
            return &ppPixmaps[ C_FILE_OBJ ];
            
        case SRC_FILE_JAVA:
        case SRC_FILE_ASM:
        case SRC_FILE_PERL:
        case SRC_FILE_PYTHON:
        case SRC_FILE_FORTH:
        case SRC_FILE_OTHER:
            return &ppPixmaps[ FILE_OBJ ];
        default:
            return &ppPixmaps[ UNKNOWN_OBJ ];
    }
    
    return &ppPixmaps[ UNKNOWN_OBJ ];
}


